package mweb

import (
	"encoding/base32"
	"errors"
	"gitee.com/dennis-mxx/mxx-core-v2/mutil"
	"github.com/goccy/go-json"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
	"xorm.io/xorm"
)

type EntitySessions struct {
	ID          int64      `xorm:"pk id autoincr bigint(20) notnull comment('id') "`
	SessionId   string     `xorm:"varchar(255) key('session_id') comment('sessionId')" json:"sessionId"`
	ServerName  string     `xorm:"varchar(255) comment('服务名称')" json:"serverName"`
	ContentType string     `xorm:"varchar(1000) comment('请求头')" json:"ContentType"`
	Value       string     `xorm:"varchar(500) comment('值')" json:"value"`
	Expire      *time.Time `xorm:"varchar(500) comment('过期时间')" json:"expire"`
	CreateTime  *time.Time `xorm:"created varchar(500) comment('创建时间')" json:"CreateTime"`
	UpdateTime  *time.Time `xorm:"updated created varchar(500) comment('修改时间')" json:"UpdateTime"`
}

func (ce *EntitySessions) TableName() string {
	return "tb_sessions"
}

type MysqlStore struct {
	Options     *sessions.Options
	MySqlClient *xorm.Engine
	Codecs      []securecookie.Codec
	serverName  string
}

func NewMysqlStore(mysqlClient *xorm.Engine, prefix string, keyPairs ...[]byte) *MysqlStore {
	return &MysqlStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		MySqlClient: mysqlClient,
		serverName:  prefix,
	}
}

func (domain *MysqlStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(domain, name)
}

func (domain *MysqlStore) New(r *http.Request, name string) (*sessions.Session, error) {
	session := sessions.NewSession(domain, name)
	opts := *domain.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, domain.Codecs...)
		if err == nil {
			err = domain.loadValue(session.ID, &session.Values, r.Header.Get("Content-Type"))
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

func (domain *MysqlStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	if session.Options.MaxAge <= 0 {
		if err := domain.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
		return nil
	}
	if session.ID == "" || session.IsNew {
		// Because the ID is used in the filename, encode it to
		// use alphanumeric characters only.
		session.ID = strings.TrimRight(
			base32.StdEncoding.EncodeToString(
				securecookie.GenerateRandomKey(32)), "=")
	}
	if err := domain.save(r, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		domain.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	return nil
}
func (domain *MysqlStore) loadValue(sessionId string, dest *map[interface{}]interface{}, contentType string) error {
	entity := &EntitySessions{}

	err := domain.MySqlClient.Where(map[string]string{
		"session_id":   sessionId,
		"server_name":  domain.serverName,
		"content_type": contentType,
	}).Find(entity)
	if err != nil {
		return err
	}
	if entity != nil {
		if entity.Expire.Unix() < mutil.DateUtil.NowDateTime().Unix() {
			domain.MySqlClient.ID(entity.ID).Delete()
			return errors.New("session is overtime")
		}
		if entity.Value != "" {
			err := json.Unmarshal([]byte(entity.Value), &dest)
			if err != nil {
				return err
			}
		}
		return nil
	}
	return errors.New("session is not found")
}
func (domain *MysqlStore) delete(session *sessions.Session) error {
	_, err := domain.MySqlClient.Where(map[string]string{"session_id": session.ID}).Delete()
	return err
}
func (domain *MysqlStore) save(r *http.Request, session *sessions.Session) error {
	newMap := make(map[string]interface{})
	for k, v := range session.Values {
		if ks, b := k.(string); b {
			newMap[ks] = v
		} else {
			panic("unsupported not string key")
		}
	}
	if b, err := json.Marshal(&newMap); err == nil {
		v := string(b)
		e := mutil.DateUtil.NowDateTime().Add(time.Duration(session.Options.MaxAge) * time.Second)
		if session.IsNew {
			_, err := domain.MySqlClient.InsertOne(&EntitySessions{
				SessionId:   session.ID,
				ServerName:  domain.serverName,
				ContentType: r.Header.Get("Content-Type"),
				Value:       v,
				Expire:      &e,
			})
			return err
		} else {
			_, err := domain.MySqlClient.Where(map[string]string{
				"session_id":  session.ID,
				"server_name": domain.serverName,
			}).Cols("expire", "value").Update(EntitySessions{Expire: &e, Value: v})
			return err
		}

	} else {
		return err
	}
}
